#include "common.h"
#include "memory.h"
#include <string.h>
#include <elf.h>

#define ELF_OFFSET_IN_DISK 0

#ifdef HAS_DEVICE
void ide_read(uint8_t *buf, uint32_t offset, uint32_t len);
#else
void ramdisk_read(uint8_t *buf, uint32_t offset, uint32_t len);
#endif

#define STACK_SIZE (1 << 20)

void create_video_mapping();
uint32_t get_ucr3();

uint32_t loader() {
	Elf32_Ehdr *elf;
	Elf32_Phdr *ph = NULL;

	uint8_t buf[4096];

#ifdef HAS_DEVICE
	ide_read(buf, ELF_OFFSET_IN_DISK, 4096);
#else
	ramdisk_read(buf, ELF_OFFSET_IN_DISK, 4096);
#endif

	elf = (void *)buf;

	{
		const uint32_t elf_magic = 0x464C457F; /* 0x7F 'E' 'L' 'F' (LE) */
		uint32_t *p_magic = (uint32_t *)buf;
		nemu_assert(*p_magic == elf_magic);
	}

	uint32_t ph_size = elf->e_phentsize * elf->e_phnum;
	uint8_t ph_buf[4096];
	nemu_assert(ph_size <= sizeof(ph_buf));

#ifdef HAS_DEVICE
	ide_read(ph_buf, ELF_OFFSET_IN_DISK + elf->e_phoff, ph_size);
#else
	ramdisk_read(ph_buf, ELF_OFFSET_IN_DISK + elf->e_phoff, ph_size);
#endif

	Elf32_Phdr *phdrs = (Elf32_Phdr *)ph_buf;

	uint32_t entry = elf->e_entry;
	int i;
	for (i = 0; i < elf->e_phnum; i++) {
		ph = &phdrs[i];
		if (ph->p_type == PT_LOAD) {
#ifdef IA32_PAGE
			mm_malloc(ph->p_vaddr, ph->p_memsz);
#endif
#ifdef HAS_DEVICE
			ide_read((uint8_t *)ph->p_vaddr,
			         ELF_OFFSET_IN_DISK + ph->p_offset,
			         ph->p_filesz);
#else
			ramdisk_read((uint8_t *)ph->p_vaddr,
			             ELF_OFFSET_IN_DISK + ph->p_offset,
			             ph->p_filesz);
#endif
			/* 清零 BSS: [p_vaddr + filesz, p_vaddr + memsz) */
			if (ph->p_memsz > ph->p_filesz) {
				memset((void *)(ph->p_vaddr + ph->p_filesz), 0,
				       ph->p_memsz - ph->p_filesz);
			}

#ifdef IA32_PAGE
			{
				extern uint32_t cur_brk, max_brk;
				uint32_t new_brk = ph->p_vaddr + ph->p_memsz - 1;
				if (cur_brk < new_brk) {
					max_brk = cur_brk = new_brk;
				}
			}
#endif
		}
	}

#ifdef IA32_PAGE

	mm_malloc(KOFFSET - STACK_SIZE, STACK_SIZE);

#ifdef HAS_DEVICE
	create_video_mapping();
#endif

	write_cr3(get_ucr3());
#endif

	return entry;
}
